<?php
namespace Pinenut\Service\Dispatcher;
/**
 * 
 * Диспечер действий, упаковывающий действия
 * 
 * Получает вызовы на исполнение действий, исполняет, возвращает результат
 * 
 * 
 * @author kosh
 *
 */
use Pinenut\Service\Dispatcher;

class Packing extends Dispatcher {
	
	static protected $doneResponses = array(); // массив готовых действий
 	static protected $deferredRequests = array(); // массив отложенных действий
 	static protected $parentStack = array(); // стек действий добавляем и удаляем из него по началу и завершению
 	static public $childrenKeys = array(); // ключи дочерних действий
	
	/**
	 * Сделать действие
	 * Находим соответствующий класс и запускаем его
	 *
	 *
	 * @param \Pinenut\Request $request
	 */	
/*
	static function action(\Pinenut\Request $request, $disableDeferred = false) {
		
		// получаем имя класса и проверяем - существует ли такое
		$class = static::action2class($request->action);
		if (!class_exists($class))
			throw new \Exception("Action '".htmlspecialchars($request->action)."' ($class) not found!", 404);
			//phpinfo();
			
		// получаем ключ
		$key = $class::key($request);
		
					
		// добавляем действие в список или обнаруживаем ранее добавленное такое же
		if (isset(static::$doneResponses[$key])) {
			$response = static::$doneResponses[$key];
			static::$childrenKeys[end(static::$parentStack)][] = $key;
			
			// проверяем - возможно ли отложенное выполнение
		} elseif ($request->deferred && !$disableDeferred) {
			// должны установить в ответ соответсвующий статус
			// добавить к списку отложенных действий это, с указанием родителя,
			// чтобы потом когда родительское действие закончится, могли возможность 
			// накатить все отложенные действия одним махом
			$response = new \Application::$ResponseClass;
			$response->content = static::deferredPlaceholder($key);
			
			static::$deferredRequests[end(static::$parentStack)][$key] = $request;
			static::$childrenKeys[end(static::$parentStack)][] = $key;
			
		} else {
			// устанавливаем родительский стэк
			array_push(static::$parentStack, $key);
			
			$action = new $class($request);
			if ($action instanceof \Pinenut\Action) {						
				
				$stages = $action->stages();
				//print_r($stages);

				foreach ($stages as $stage) {
				
					// выполняем действие
					$response = $action->run($stage);
				
					// если у нас накопились отложенные действия для этого действия - выполняем их
					if (isset(static::$deferredRequests[$key])) {
						$deferredRequests = static::$deferredRequests[$key];
						foreach ($deferredRequests as $k=>$defferedRequest) {
							// исполним для начала
							$defferedResponse = static::action($defferedRequest, true);
						}
						$patterns = $replacements = array();
						foreach ($deferredRequests as $k=>$defferedRequest) {
							// а теперь позаменяем плейсхолдеры
							$patterns[] = '/'.preg_quote(static::deferredPlaceholder($k), '/') . '/';
							$replacements[] = static::$doneResponses[$k]->content;
						}
						$response->content = preg_replace($patterns, $replacements, $response->content);
					}
					unset(static::$deferredRequests[$key]);
					
					// накатываем каналы
					if (isset(static::$childrenKeys[$key]) 
							&& is_array(static::$childrenKeys[$key])) {
						foreach (static::$childrenKeys[$key] as $childKey) {
							// для каждого дочернего действия берём его готовый ответ и накатываем его на текущий
							if (isset(static::$doneResponses[$childKey])) 
								$response->play(static::$doneResponses[$childKey]);
						}
					}
					
				}
				
			} else
				throw new \Exception("Action '".htmlspecialchars($request->action)."' ($class) not support!", 500);
				
			array_pop(static::$parentStack);
			
			static::$doneResponses[$key] = $response;
			
			if (!$request->deferred) 
				static::$childrenKeys[end(static::$parentStack)][] = $key;
			
			
		}
			


		return $response;
	} 
*/	
 
	
	/**
	 * Отсеиваем уже выполненные запросы и те, что можно выполнить лениво
	 * Остальные опять собираем в массив и отдаём исполнителю
	 * 
	 * @param array|\Pinenut\Request  $requests массив запросов, или один запрос
	 * @param unknown_type $disableDeferred
	 * @throws \Exception
	 */
	
	static function action($requests, $disableDeferred = false) {
 		
 		$isAlone = false;
 		// подготовили массив
 		if (!is_array($requests)) {
 			$requests = array($requests);
 			$isAlone = true;
 		} else {
 //			print_r($requests);
 		}
 		
 		// инициируем массив ответов
 		$responses = array();
 		// и массив ключей
 		$keys = array();
 		
 		// массив выполняемых сразу запросов
 		$executiveRequests = array();
 		
 		// подготовительный цикл
 		foreach ($requests as $requestKey => $request) {

 			// получаем ключ запроса
			$key = $request->key();

 			// добавляем действие в список или обнаруживаем ранее добавленное такое же
 			if (isset(static::$doneResponses[$key])) {
 				$responses[$requestKey] = static::$doneResponses[$key];
 				static::$childrenKeys[end(static::$parentStack)][] = $key;
 			
 				// проверяем - возможно ли ленивого выполнение
 			} elseif ($request->deferred && !$disableDeferred) {
 				// должны установить в ответ соответсвующий статус
 				// добавить к списку отложенных действий это, с указанием родителя,
 				// чтобы потом когда родительское действие закончится, могли возможность
 				// накатить все отложенные действия одним махом
 				$response = new \Application::$ResponseClass;
 				$response->content = static::deferredPlaceholder($key);
 			
 				static::$deferredRequests[end(static::$parentStack)][$key] = $request;
 				static::$childrenKeys[end(static::$parentStack)][] = $key;
 			
 				$responses[$requestKey] = $response;
 			} else {
 				
 				// для реально выполняемых запросов - собираем их в кучу, чтоб оптом отдать исполнителю
 				$executiveRequests[$key] = $request;
 				$executiveKeys[$key] = $requestKey;
 				
 			}
 			
 			
 		}
 		
 		// исполняем пачку запросов и расписываем ответы в общий массив
 		$executiveResponses = self::execute($executiveRequests);
 		
 		foreach ($executiveResponses as $key=>$response) {
 			$responses[$executiveKeys[$key]] = $response;
 		 }
 		
 		

 		if ($isAlone) 
 			return array_shift($responses); 
 		else 
			return $responses;
	}
	
	
	/**
	 * Задача реализации этого исполнителя в минимизации обращений к кеш 
	 * за счет группировки запросов к нему в multiget запросы
	 * 
	 * на входе массив запросов, на выходе массив ответов соответствующих входящим
	 * 
	 * 
	 * выполнение одиночного запроса:
	 * 
	 * - получение этапов выполения
	 * - для каждого этапа начиная с последнего:
	 * 		- формируем ключ кеш
	 * 		- ищем индекс привилегий этапа в кеш (два запроса - данные и тэги):
	 * 			- если находим, то проверяем привилегии:
	 * 				- если находим подходящий ключ, то запрашиваем его (два запроса - данные и тэги)
	 * 					- если находим данные - выходим из цикла по этапам
	 * 		- если не нашли чего-то - переходи к следующему этапу, добавляя этот этап в план выполнения
	 * 	- для каждого из этапов в очереди:
	 * 		- исполняем этап
	 * 		- сохраняем ответ в кеш (данные) (два запроса - получить тэги сохранить данные)
	 * 		- модифицируем соответствующий индекс привилегий в соответствии 
	 * 			с привилегиями (два запроса - получить тэги сохранить данные)
	 * 	- ура, у нас есть ответ!
	 * 
	 * Цель доработки - минимизация количества запросов к кешу за счет упаковки нескольких запросов в один мультизапрос - так, 
	 * чтобы кол-во мультизапросов было пропорционально количеству этапов с коэффициентов - сложности выполнения этапа
	 * 
	 * выполенение пачки запросов:
	 * 
	 * - получение этапов выполнения для каждого запроса, вычисляем максимальное кол-во этапов
	 * - цикл по макс кол-ву этапов:
	 * 		- для каждого запроса получаем последний этап, если остался
	 * 			- формируем ключ кеш и сохраняем его в список на получение индекса привилегий
	 * 		- получаем индексы привилегий для всего списка (два запроса - данные и тэги - должно быть реализовано в адаптере)
	 * 		- для каждого запроса получаем последний этап, если остался
	 * 			- проверяем привилегии
	 * 				- если находим подходящий ключ - добавляем его в список получения данных
	 * 		- получаем данные по списку ключей одним запросом
	 * 		- для каждого запроса получаем последний этап, если остался
	 * 			- если данные найдены - обнуляем кол-во оставшися этапов
	 * 			- иначе добавляем этап запроса в список на выполнения удалив его из списка этапов запроса
	 * - для каждого запроса для которого этапы были добавлены в очереди на выполнение:
	 * 		- для каждого из этапов в очереди:
	 * 			- исполняем этап
	 * 			- сохраняем ответ в кеш (данные) (два запроса - получить тэги сохранить данные)
	 * 			- модифицируем соответствующий индекс привилегий в соответствии 
	 * 				с привилегиями (два запроса - получить тэги сохранить данные) 
	 * - ура, у нас есть куча ответов!
	 * 
	 * @param unknown_type $requests
	 * @throws \Exception
	 * @return Ambigous <multitype:, unknown>
	 */
	protected static function execute($requests) {
		
		$responses = array();
		
		foreach ($requests as $key=>$request) {
			// а вот тут мы должны реально исполнять пачку запросов...
			// но а пока - просто переформатируем, чтоб эта пачка отправлялась на исполнение для ленивых

			
			// проверяем, что это запросы и что классы существуют и они - запросы
			if ($request instanceof  \Pinenut\Request) {
				$class = static::action2class($request->action);
				if (!class_exists($class))
					throw new \Exception("Action '".htmlspecialchars($request->action)."' ($class) not found!", 404);
			} else {
				throw new \Exception("Wrong request type", 500);
			}
			
			
			// устанавливаем родительский стэк
			array_push(static::$parentStack, $key);
			
			$action = new $class($request);
			if ($action instanceof \Pinenut\Action) {
			
				$stages = $class::stages($request);
				//print_r($stages);
				
				$response = null;
				
				foreach ($stages as $stage) {
					// выполняем действие
					$response = $action->run($stage, $response);
					
					// выполняем отложенные действия (пачку разом)
					$response = static::executeDeferred($key, $response);
					
					// накатываем изменения данных каналов
					$response = static::playChannels($key, $response);		
				}
				


			
			} else
				throw new \Exception("Action '".htmlspecialchars($request->action)."' ($class) not support!", 500);
			
			array_pop(static::$parentStack);
			
			
			static::$doneResponses[$key] = $responses[$key] = $response;
			
			if (!$request->deferred)
				static::$childrenKeys[end(static::$parentStack)][] = $key;
		}
		return $responses;
	}
	
	static function executeDeferred($key, $response) {
		// если у нас накопились отложенные действия для этого действия - выполняем их
		if (isset(static::$deferredRequests[$key])) {
			$deferredRequests = static::$deferredRequests[$key];
			// вместо внутреннего цикла - запросим сразу паску ответов
			$defferedResponses = static::action($deferredRequests, true);
		
			$patterns = $replacements = array();
			foreach ($defferedResponses as $k=>$defferedResponse) {
				// а теперь позаменяем плейсхолдеры
				$patterns[] = '/'.preg_quote(static::deferredPlaceholder($k), '/') . '/';
				$replacements[] = $defferedResponse->content; //static::$doneResponses[$k]->content;
			}
			$response->content = preg_replace($patterns, $replacements, $response->content);
		}
		unset(static::$deferredRequests[$key]);
		
		return $response;
	}
	
	static function playChannels($key, $response) {
		// накатываем каналы
		if (isset(static::$childrenKeys[$key])
				&& is_array(static::$childrenKeys[$key])) {
			foreach (static::$childrenKeys[$key] as $childKey) {
				// для каждого дочернего действия берём его готовый ответ и накатываем его на текущий
				if (isset(static::$doneResponses[$childKey]))
					$response->play(static::$doneResponses[$childKey]);
			}
		}
		return $response;
	}
	
	/**
	 * Преобразование названия действия в имя класса
	 * По умолчанию используются namespace, и в нем класс, с заглавной буквы и постфиксом "Action.php"
	 */
	static protected function action2class($action) {
		// собираем класс
		$path = explode('.', $action);
		$path = array_map(function ($part) {
			return ucfirst(strtolower($part));
		}, $path);
		//print_r($path);
		return implode('\\', $path).'Action';
	}
	
	static protected function deferredPlaceholder($key) {
		return '<!-- DA:'.$key.' -->';
	}
	

}
